import sys

from .util import high_byte, low_byte

def message_write(file_obj, byte_list, debug=0):
    def encode(b):
        if b == ord('\\'):
            return '\\''\\'
        elif b == ord('\n'):
            return '\\''n'
        else:
            return chr(b)
    out = ''.join([encode(x) for x in byte_list]+['\n'])
    if debug:
        print >> sys.stderr, 'message_write: ', [ ord(x) for x in out]
    file_obj.write(out)
    file_obj.flush()

class RPCError(Exception):
    pass
class ResponseError(RPCError):
    pass
class MessageReadError(RPCError):
    pass
def message_read(file_obj, debug=0):
    #FIXME: ugly implementation, factor out msg
    def helper():
        in_escape = 0
        msg = []
        while 1:
            c = file_obj.read(1)
            if c:
                msg.append(ord(c))
            if c == '':
                raise MessageReadError(msg) # short read
            elif (not in_escape) and c == '\\':
                in_escape = 1
            else:
                if c == '\n':
                    break
                elif c == '\\':
                    yield '\\'
                elif c == 'n':
                    if in_escape:
                        yield '\n'
                    else:
                        yield 'n'
                else:
                    yield c
                in_escape = 0
        if debug:
            print >> sys.stderr, 'message_read: ', msg
    return [ ord(x) for x in helper() ]

(METHOD_MESSAGE_MAX,
 METHOD_ECHO,
 METHOD_LED_SET,
 METHOD_LED_TOGGLE,
 METHOD_PORTD_SET,
 METHOD_PORTC_SET,
 METHOD_SERVOA_SET,
 METHOD_SERVOB_SET,

 METHOD_ACS_SETUP,
 METHOD_ACS_PICKNEW,
 METHOD_ACS_TURN,
 METHOD_ACS_THROW_ONE,
 METHOD_ACS_THROW_TWO,
 METHOD_ACS_BACK,

 METHOD_ACS_NEXT,
 METHOD_ACS_HANDOUT,
 METHOD_ACS_HANDIN,
 METHOD_ACS_END,

 METHOD_LED_GREEN_SET,
 METHOD_LED_RED_SET,
 METHOD_LED_WHITE_SET,

 METHOD_NUM_SET,
 METHOD_BEEP,

 METHOD_FAN_SET,
) = range(24)
METHOD_TEST = 0xff
(PROTOCOL_RPC_RETURN,
PROTOCOL_DEBUG_MESSAGE,
PROTOCOL_UNKNOWN_METHOD,
) = range(3)
class UnknownMethod(RPCError): # indicated by remote host
    pass
class EchoError(RPCError):
    pass
class RomRead64Error(RPCError):
    pass
class Connection(object):
    def __init__(self, file_obj, debug):
        self.f = file_obj
        self.debug = debug
    def _call(self, method, byte_list):
        try:
            message_write(self.f, [method] + byte_list, self.debug)
            r = message_read(self.f, self.debug)
        except IOError, e:
            raise RPCError(e)
        else:
            if not r:
                raise ResponseError(r)
            elif r[0] == PROTOCOL_UNKNOWN_METHOD and len(r) == 2:
                raise UnknownMethod(r[1])
            elif r[0] == PROTOCOL_RPC_RETURN:
                return r[1:]
            else:
                raise ResponseError(r)
    def message_max(self):
        return self._call(METHOD_MESSAGE_MAX, [])[0]

    def echo(self, byte_list):
        r = self._call(METHOD_ECHO, byte_list)
        if r != byte_list:
            raise EchoError(byte_list, r)

    def led_set(self, b):
        self._call(METHOD_LED_SET, [ b ])

    def led_toggle(self):
        self._call(METHOD_LED_TOGGLE, [ ])

    def portd_set(self, b):
        self._call(METHOD_PORTD_SET, [ b ])

    def portc_set(self, b):
        self._call(METHOD_PORTC_SET, [ b ])

    def servoa_set(self, b):
        self._call(METHOD_SERVOA_SET, [ b ])

    def servob_set(self, b):
        self._call(METHOD_SERVOB_SET, [ b ])

    def acs_picknew(self):
        self._call(METHOD_ACS_PICKNEW, [ ])

    def acs_setup(self):
        self._call(METHOD_ACS_SETUP, [ ])

    def acs_turn(self):
        self._call(METHOD_ACS_TURN, [ ])

    def acs_throw_one(self):
        self._call(METHOD_ACS_THROW_ONE, [ ])

    def acs_throw_two(self):
        self._call(METHOD_ACS_THROW_TWO, [ ])

    def acs_back(self):
        self._call(METHOD_ACS_BACK, [ ])

    def acs_next(self):
        self._call(METHOD_ACS_NEXT, [ ])

    def acs_handout(self):
        self._call(METHOD_ACS_HANDOUT, [ ])

    def acs_handin(self):
        self._call(METHOD_ACS_HANDIN, [ ])

    def acs_end(self):
        self._call(METHOD_ACS_END, [ ])

    def led_green_set(self, b):
        self._call(METHOD_LED_GREEN_SET, [ b ])

    def led_red_set(self, b):
        self._call(METHOD_LED_RED_SET, [ b ])

    def led_white_set(self, b):
        self._call(METHOD_LED_WHITE_SET, [ b ])

    def num_set(self, b):
        self._call(METHOD_NUM_SET,[ b ])

    def beep(self):
        self._call(METHOD_BEEP,[ ])

    def fan_set(self, b):
        self._call(METHOD_FAN_SET,[ b ])

    if 0:
        def mpu_software_reset(self):
            self._call(METHOD_MPU_SOFTWARE_RESET, [])
        def jump_to_ldrom(self):
            self._call(METHOD_JUMP_TO_LDROM, [])
        def timer0_set(self, t):
            self._call(METHOD_TIMER0_SET, [high_byte(t), low_byte(t)])
        def isp_init(self):
            self._call(METHOD_ISP_INIT, [])
        def rom_erase(self):
            self._call(METHOD_ROM_ERASE, [])
        def rom_read(self, address):
            return self._call(METHOD_ROM_READ,
                            [high_byte(address), low_byte(address) ])[0]
        def rom_write_init(self):
            self._call(METHOD_ROM_WRITE_INIT, [])
        def rom_write(self, value):
            self._call(METHOD_ROM_WRITE, [value])
        def rom_write_message_max(self, bytes):
            self._call(METHOD_ROM_WRITE, bytes)
        def idle_mode_enter(self):
            message_write(self.f, [METHOD_IDLE_MODE_ENTER])
        def timer0_run_set(self, v):
            self._call(METHOD_TIMER0_RUN_SET, [v])
        def chpcon_read(self):
            return self._call(METHOD_CHPCON_READ, [])[0]
        def chpcon_write(self, v):
            self._call(METHOD_CHPCON_WRITE, [v])
        def xdata_read(self, address):
            return self._call(METHOD_XDATA_READ,
                            [high_byte(address), low_byte(address)])[0]
        def xdata_write(self, address, v):
            self._call(METHOD_XDATA_WRITE,
                            [high_byte(address), low_byte(address), v])
        def idata_read(self, address):
            return self._call(METHOD_IDATA_READ, [address])[0]
        def idata_write(self, address, v):
            self._call(METHOD_IDATA_WRITE, [address, v])
        def idata_write_32(self, address, bytes):
            self._call(METHOD_IDATA_WRITE_32, [address] + bytes)
        def rom_read_64(self, address):
            r = self._call(METHOD_ROM_READ_64,
                            [high_byte(address), low_byte(address)])
            if len(r) != 64:
                raise RomRead64Error(address, r)
            return r
        def sfrah_set(self, v):
            self._call(METHOD_SFRAH_SET, [v])
        def sfral_set(self, v):
            self._call(METHOD_SFRAL_SET, [v])
        def sfrfd_write(self, v):
            self._call(METHOD_SFRFD_WRITE, [v])
        def rom_fill_pattern(self, byte_list):
            self._call(METHOD_ROM_FILL_PATTERN, byte_list)
        def rom_seq_write_init(self):
            self._call(METHOD_ROM_SEQ_WRITE_INIT, [])
        def rom_seq_write(self, byte_list):
            self._call(METHOD_ROM_SEQ_WRITE, byte_list)
        def test(self):
            self._call(METHOD_TEST, [])
